'AbstractDAGComposer provides most of the protocol to lay out DAGs. The actual layout algorithm must be provided by a subclass (positionNodes).
To use it:
- First, initialize (set up graph & extentBlock, optionally: margins, direction (horizontal/vertical), spacings)
- Then, you can set up connection blocks (setting direction overwrites this)
- Then compose the graph,
- Finally, you can access the positions of the nodes and lines.
Some possible improvements:
- Allow reversed graphs (roots at right or bottom)
- Better layout (improved depth weighting)
- protocol to specify order of roots
Instance variables:
graph - The <RootedGraph> being composed.
views - A Dictionary from nodes in the RootedGraph to views (VisualComponents)
leafMargin - <Integer> the amount of space to leave at the left of the composition
rootMargin - <Integer> the amount of space to leave between the roots and the edge
sideMargin - <Integer> the amount of space to leave at the sides of the graph
horizontal - <Boolean> true if the layout is horizontal (roots at left), false if vertical (roots at top)
minPCspace - <Integer> the minimum spacing between parents and children in the layout
minSiblingSpace - <Integer> the minimum spacing between siblings
positions - A Dictionary from nodes in the graph to the positions of their views (Points) in the layout
graphExtent - A <Point> representing the extent of the composed graph
lines - An <OrderedCollection of: Array> where each Array has three elements:
1. Start <Point> 2. End <Point> 3. <Object|nil> representation of the label for that edge.
extents - A <Dictionary> mapping graph nodes to the extents of their views
extentBlock - A block that takes a node''s view and returns the view''s extent
inBlock - A block that, given the views at the end and start of an edge, and the bounding box of the former, returns a suitable end <Point> for the edge
outBlock - As above, but returns a start <Point>
labels - A <Dictionary> mapping an Array of (start node, end node, label) for each edge to the object
representing the label on the edge
labelPositions - A <Dictionary> from labels to positions
labelPositionBlock - A block that given the start and end <Points> of each edge, and the label for that edge, returns a <Point> describing where the label should be positioned.
!AbstractDAGComposer class methodsFor: 'instance creation'!
new
^super new initialize! !
AbstractDAGComposer subclass: #TreeComposer
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
category: 'Tools-Grapher'!
TreeComposer comment:
'A TreeComposer can lay out a tree.
The layout algorithm is simple, based on bounding boxes of sub-trees:
- the bounding box of a leaf is that of its view
- the bounding box of a sub-tree is obtained by laying out its sub-trees next to each other, then placing the node at the root of the subtree above those, centered.
'!
!TreeComposer methodsFor: 'private-composing'!
boundingBoxes
"The default bounding box of a subtree is the extent of the root of the subtree (which is correct for leaves, and will be fixed..."
'A DAGComposer can lay out DAGs. It uses a simple-minded algorithm: each node has its depth calculated (the maximum path length from a root), and then all the nodes with the saem depth are layed out next to each other. A simple heuristic tries to keep nodes below their parents.'!
!DAGComposer methodsFor: 'private-composing'!
nodeOrdering
"This method lays out the nodes into rows. All nodes at the same depth in the DAG are on the same row. The ordering within a row is determined by 'closeness' to predecessors."
| dl depths weights roots inc p rootList i |
"First partition the graph into sets of nodes at the same depth."
dl := graph depthList.
"Next build a map from nodes to depths."
depths := Dictionary new.
1 to: dl size do: [ :depth |
(dl at: depth) do: [ :node |
depths at: node put: depth]].
"The rest of the method calculates the ordering of nodes at a particular depth.
To do this, it weights each node with the sum of the weights of its immediate predecessors, and then orders by weight.
When weighting a node, this particular implementation only considers predecessors with depth one less than the node (should be fixed)."
'A GraphView is used to draw a graphical representation of the relationships between objects. The graphical relation is represented by a Graph.
Graphs have nodes, edges and (optionally) labels on edge. In the graphical representation each node is represented by a view; the viewBlock is used to obtain a view object for a node. Each label is also represented by a view; the labelBlock is used to build these. It can be set to nil if there are no labels, or labels are not to be displayed (this is the default).
The code used for rendering lines is also pluggable: see lineBlock.
The other aspect of a GraphView is the composer, which is an object that "knows" how to layout the views given the structure of the graph. TreeComposers can do a reasonable job for trees; DAGComposers can do directed acyclic graphs. Lots more composers will be provided one day!!
See the examples to get an idea how to use GraphViews.
Mario Wolczko, 1991
Instance variables:
composer <GraphComposer> the composer used to lay out the graph.
viewBlock <Block of: Object to: VisualComponent>
used the obtain a view for each node in the graph
lineBlock <Block of: GraphicsContext to: (Block of: Point of: Point of: Object)>
used to obtain a block which can display a line given the start point, end point and label.
labelBlock <Block of: Object of: Object of: Object to: Object>
used to obtain the object that will represent the label of an edge, given the node at the source of the edge, the destination node, and the label in the graph.
nodeViews <IdentityDictionaryWithDefault from: Object to: VisualComponent>
the cached output of viewBlock
labelViews <DictionaryWithDefault from: Array of 3 Objects to: Object> the cached output of labelBlock
"Build a GraphView on the graph. Use aComposer to lay out the graph. Use aBlock with each graph node to build a view on that node. Use lblBlock with each edge label to build a view on that label."
"Build a GraphView on the graph. Use aComposer to lay out the graph. Use aBlock with each graph node to build a view on that node. Use lblBlock with each edge label to build a view on that label. Use lineBlock (if not nil) to render lines."
"Open a ScheduledWindow labelled with label containing a GraphView viewing the graph. Use aComposer to lay out the graph. Assume that each node and label should be displayed by sending it printString."
"Open a ScheduledWindow labelled with label containing a GraphView viewing the graph. Use aComposer to lay out the graph. Use aBlock with each graph node to build a view on that node."
"Open a ScheduledWindow labelled with label containing a GraphView viewing the graph. Use aComposer to lay out the graph. Use aBlock with each graph node to build a view on that node. Use lblBlock with each edge label to build a view on that label."
"Open a ScheduledWindow labelled with label containing a GraphView viewing the graph. Use aComposer to lay out the graph. Use aBlock with each graph node to build a view on that node. Use lblBlock with each edge label to build a view on that label. Use lineBlock to render the lines (see lineBlock: instance method)."
"Open a ScheduledWindow labelled with label containing a GraphView viewing the graph. Use a default composer to lay out the graph. Assume that each node and label should be displayed by sending it printString."
"Build a scrolling view containing a GraphView viewing the graph. Use aComposer to lay out the graph. Use aBlock with each graph node to build a view on that node."
"Build a scrolling view containing a GraphView viewing the graph. Use aComposer to lay out the graph. Use aBlock with each graph node to build a view on that node. Use lblBlock with each label to build a view on that label."
"Build a scrolling view containing a GraphView viewing the graph. Use aComposer to lay out the graph. Use aBlock with each graph node to build a view on that node. Use lblBlock with each label to build a view on that label. Use lineBlock to render the lines (see lineBlock: instance method)."
| view decorator |
view := self
model: graph
composer: aComposer
viewBlock: aBlock
labelBlock: lblBlock
lineBlock: lineBlock.
UseCachingWrapperAsDefault ifTrue: [view := CachingWrapper on: view].
decorator := LookPreferences edgeDecorator onScroller: (PanningWrapper on: view).
decorator useHorizontalScrollBar.
^decorator!
windowOn: graph composer: aComposer
"Build a ScheduledWindow containing a GraphView viewing the graph. Use aComposer to lay out the graph. Assume that each node and label should be displayed by sending it printString."
"Build a ScheduledWindow containing a GraphView viewing the graph. Use aComposer to lay out the graph. Use aBlock with each graph node to build a view on that node."
"Build a ScheduledWindow containing a GraphView viewing the graph. Use aComposer to lay out the graph. Use aBlock with each graph node to build a view on that node. Use lblBlock with each label to build a view on that label."
"Build a ScheduledWindow containing a GraphView viewing the graph. Use aComposer to lay out the graph. Use aBlock with each graph node to build a view on that node. Use lblBlock with each label to build a view on that label. Use lineBlock to render the lines (see lineBlock: instance method)."
| topView view |
view := self
viewOn: graph
composer: aComposer
viewBlock: aBlock
labelBlock: lblBlock
lineBlock: lineBlock.
topView := ScheduledWindow new.
topView component: view.
^topView! !
!GraphView class methodsFor: 'backward compatibility'!